/**
* Copyright (C) 2009 Original Authors
*
* This file is part of Spring ME.
*
* Spring ME is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* Spring ME is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Spring ME; see the file COPYING. If not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* Linking this library statically or dynamically with other modules is
* making a combined work based on this library. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under
* terms of your choice, provided that you also meet, for each linked
* independent module, the terms and conditions of the license of that
* module. An independent module is a module which is not derived from or
* based on this library. If you modify this library, you may extend this
* exception to your version of the library, but you are not obligated to
* do so. If you do not wish to do so, delete this exception statement
* from your version.
*/
package me.springframework.di.spring;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import me.springframework.di.Configuration;
import me.springframework.di.Instance;
import me.springframework.di.Scope;
import me.springframework.di.Sink;
import me.springframework.di.Source;
import me.springframework.di.base.MutableConfiguration;
import me.springframework.di.base.MutableConstructorArgument;
import me.springframework.di.base.MutableContext;
import me.springframework.di.base.MutableInstance;
import me.springframework.di.base.MutableInstanceReference;
import me.springframework.di.base.MutableListSource;
import me.springframework.di.base.MutableMapSource;
import me.springframework.di.base.MutablePropertySetter;
import me.springframework.di.base.MutableSource;
import me.springframework.di.base.MutableStringValueSource;
import me.springframework.di.gen.factory.BeanFactoryGenerator;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.ManagedProperties;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.Resource;
/**
* A class capable of loading lists of {@link Instance Instances} from a Spring
* application context. Note that the {@link Configuration} returned by
* {@link #load(BeanDefinitionRegistry)} will still be fairly incomplete. It
* relies on some other source to complete the metadata required to generate
* source code using
* {@link BeanFactoryGenerator#generate(me.springframework.di.gen.Destination, Configuration)}
* .
*
* @author Wilfred Springer
*
*/
public class SpringConfigurationLoader {
/**
* Something for providing artificial ids.
*/
private static int counter = 0;
/**
* The objects responsible for augmenting the model read from Spring
* configuration.
*/
private Augmentation[] augmentations;
/**
* Constructs a new instance, accepting a number of objects to augment the
* partial {@link Configuration} constructed from the Spring configuration
* files.
*
* @param augmentations
* Objects capable of augmenting the partial model constructed
* from the meta data in Spring configuration files.
*/
public SpringConfigurationLoader(Augmentation... augmentations) {
this.augmentations = augmentations;
}
/**
* Loads a Configuration from a Spring XML based application context.
*
* @param resource
* The Spring configuration file defining the beans.
* @return A {@link Configuration} representing the graph of wired objects.
*/
public Configuration load(Resource resource) {
ConfigurableListableBeanFactory registry = new XmlBeanFactory(resource);
return load(registry);
}
/**
* Loads a Configuration from an existing application context.
*
* @param resource The Spring context from which to load bean definitions.
* @return A {@link Configuration} representing the graph of wired objects.
*/
public Configuration load(ConfigurableListableBeanFactory factory) {
MutableContext context = loadBeans(factory);
for (Augmentation augmentation : augmentations) {
augmentation.augment(context);
}
return createConfiguration(context);
}
/**
* Returns a {@link Map} of {@link MutableInstance MutableInstances},
* indexed by name.
*
* @param registry
* The {@link BeanDefinitionRegistry} holding the bean
* definitions.
* @return A {@link Map} of {@link MutableInstance MutableInstances}
* representing the root beans defined by the
* {@link ListableBeanFactory}.
*/
protected static MutableContext loadBeans(ConfigurableListableBeanFactory factory) {
MutableContext context = new MutableContext();
for (String name : factory.getBeanDefinitionNames()) {
for (String alias : factory.getAliases(name)) {
context.addAlias(alias, name);
}
}
for (String name : factory.getBeanDefinitionNames()) {
BeanDefinition definition = factory.getBeanDefinition(name);
if (!definition.isAbstract()) {
BeanDefinition merged = factory.getMergedBeanDefinition(name);
MutableInstance instance = new MutableInstance(name);
load(instance, merged, context);
context.addInstance(name, instance);
}
}
return context;
}
/**
* Loads a {@link MutableInstance} from one of the {@link BeanDefinition}s
* provided by the {@link BeanDefinitionRegistry} passed in.
*
* @param instance
* A {@link MutableInstance} to be populated.
* @param definition
* A {@link BeanDefinition}, providing the meta data.
*/
private static void load(MutableInstance instance, BeanDefinition definition,
MutableContext context) {
instance.setReferencedType(definition.getBeanClassName());
instance.setPrimitive(false);
instance.setLazyInit(definition.isLazyInit());
instance.setId("source" + counter++);
instance.setFactoryMethod(definition.getFactoryMethodName());
instance.setFactoryInstance(definition.getFactoryBeanName());
if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope())) {
instance.setScope(Scope.SINGLETON);
}
if (ConfigurableBeanFactory.SCOPE_PROTOTYPE.equals(definition.getScope())) {
instance.setScope(Scope.PROTOTYPE);
}
if (definition instanceof AbstractBeanDefinition) {
instance.setInitMethod(((AbstractBeanDefinition) definition).getInitMethodName());
instance.setDestroyMethod(((AbstractBeanDefinition) definition).getDestroyMethodName());
}
if (!definition.getConstructorArgumentValues().isEmpty()) {
List<MutableConstructorArgument> arguments = new ArrayList<MutableConstructorArgument>();
for (Object object : definition.getConstructorArgumentValues()
.getGenericArgumentValues()) {
MutableConstructorArgument argument = new MutableConstructorArgument(instance);
argument.setInstance(instance);
ValueHolder holder = (ValueHolder) object;
argument.setSource(loadSource(context, argument, holder.getValue()));
argument.setType(holder.getType());
arguments.add(argument);
}
instance.setConstructorArguments(arguments);
}
Set<MutablePropertySetter> setters = new HashSet<MutablePropertySetter>();
for (Object object : definition.getPropertyValues().getPropertyValueList()) {
MutablePropertySetter setter = new MutablePropertySetter(instance);
setter.setInstance(instance);
PropertyValue value = (PropertyValue) object;
setter.setName(value.getName());
setter.setSource(loadSource(context, setter, value.getValue()));
setters.add(setter);
}
instance.setSetters(setters);
// added by woj
instance.setAutowireCandidate(definition.isAutowireCandidate());
if (definition instanceof AbstractBeanDefinition) {
instance.setAutowireMode(((AbstractBeanDefinition) definition)
.getResolvedAutowireMode());
} else {
instance.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_NO);
}
}
/**
* Loads a {@link MutableSource} by examining the value of a Sink.
*
* @param sink
* The {@link Sink} configured using a certain type of source.
* @param value
* The Spring representation of that source.
* @return A {@link MutableSource}, representing the source of the data to
* be injected in the {@link Sink}.
*/
private static MutableSource loadSource(MutableContext context, Sink sink, Object value) {
MutableSource result = null;
if (value instanceof String) {
return load(sink, (String) value);
} else if (value instanceof RuntimeBeanReference) {
result = load(sink, (RuntimeBeanReference) value, context);
} else if (value instanceof TypedStringValue) {
result = load(sink, (TypedStringValue) value);
} else if (value instanceof BeanDefinitionHolder) {
result = load(sink, (BeanDefinitionHolder) value, context);
} else if (value instanceof ManagedList) {
result = load(sink, (ManagedList) value, context);
} else if (value instanceof ManagedMap) {
result = load(context, sink, (ManagedMap) value);
} else if (value instanceof ManagedProperties) {
result = load(sink, (ManagedProperties) value);
} else {
System.err.println("No support for " + value.getClass().getName());
return null;
}
result.setId("source" + (counter++));
return result;
}
private static MutableSource load(Sink sink, String s) {
return new MutableStringValueSource(sink, s, "java.lang.String");
}
private static MutableSource load(Sink sink, ManagedProperties managedProperties) {
throw new UnsupportedOperationException("No support for managed properties yet.");
}
private static MutableSource load(MutableContext context, Sink sink,
ManagedMap value) {
MutableMapSource source = new MutableMapSource(sink);
for (Object element : value.entrySet()) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) element;
MutableMapSource.MapSourceEntry created = new MutableMapSource.MapSourceEntry();
Sink keySink = new EntrySink(EntrySink.Type.Key, source);
Sink valueSink = new EntrySink(EntrySink.Type.Value, source);
created.setKey(loadSource(context, keySink, entry.getKey()));
created.setValue(loadSource(context, valueSink, entry.getValue()));
source.getEntries().add(created);
}
return source;
}
/**
* Returns a {@link MutableSource} from a source providing a list of values.
*
* @param sink
* The {@link Sink} that has the result of this object configured
* as its source.
* @param list
* The Spring representation of a list of values.
* @return The {@link Source} representation of the object producing that
* list of values.
*/
private static MutableSource load(Sink sink, ManagedList list, MutableContext context) {
ArrayList<MutableSource> elements = new ArrayList<MutableSource>();
MutableListSource source = new MutableListSource(sink, elements);
int index = 0;
for (Object object : list) {
elements.add(loadSource(context, new ElementSink(index++, source), object));
}
return source;
}
/**
* Constructs a {@link MutableSource} from a source providing an anonymous
* bean.
*
* @param sink
* The {@link Sink} configured to receive the value produced by
* the source.
* @param value
* The actually value constructed.
* @return The {@link Source} representation of the object producing that
* anonymous bean.
*/
private static MutableSource load(Sink sink, BeanDefinitionHolder value,
MutableContext context) {
MutableInstance instance = new MutableInstance(sink, value.getBeanName());
load(instance, value.getBeanDefinition(), context);
return instance;
}
/**
* Constructs a {@link MutableSource} from a source based on a literal
* representation of a value.
*
* @param sink
* The {@link Sink} configured to receive the value produced by
* the source.
* @param value
* Spring's representation of that value.
* @return The {@link Source} representation of the object producing that
* value.
*/
private static MutableSource load(Sink sink, TypedStringValue value) {
return new MutableStringValueSource(sink, value.getValue(), value.getTargetTypeName());
}
/**
* Constructs a {@link MutableSource} from a source based on a reference to
* a bean defined somewhere else.
*
* @param sink
* The {@link Sink} configured to receive the value produced by
* the source.
* @param value
* Spring's representation of the object producing the data to be
* injected.
* @return The {@link Source} representation of the object producing that
* value.
*/
private static MutableSource load(Sink sink, RuntimeBeanReference value, MutableContext context) {
String name = value.getBeanName();
return new MutableInstanceReference(sink, name);
}
private static class EntrySink implements Sink {
private MutableSource source;
private Type type;
public enum Type {
Key, Value;
}
public EntrySink(Type type, MutableSource source) {
this.type = type;
this.source = source;
}
public Instance getInstance() {
return null;
}
public Source getSource() {
return source;
}
public String getType() {
return "java.lang.Object";
}
public boolean isPrimitive() {
return false;
}
public String getCastTo() {
return null;
}
public String toString() {
switch (type) {
case Key:
return "the key of an entry of " + source.toString();
case Value:
return "the value of an entry of " + source.toString();
default:
return null; // Keep compiler happy
}
}
}
/**
* An artificial {@link Sink} class, representing a certain element of a
* list injected into field. Introduced later to have the ability to refer
* that element directly, when generating a reference to a certain instance
* defined in the configuration.
*
* @author Wilfred Springer (wis)
*
*/
private static class ElementSink implements Sink {
/**
* The index of the element.
*/
private int index;
/**
* The {@link Source} from which it will obtain its data.
*/
private MutableSource source;
/**
* Constructs a new instance.
*
* @param index
* The index of the List element.
* @param source
* The {@link Source} producing the data.
*/
public ElementSink(int index, MutableSource source) {
this.index = index;
this.source = source;
}
/*
* (non-Javadoc)
*
* @see me.springframework.di.Sink#getInstance()
*/
public Instance getInstance() {
return null;
}
/*
* (non-Javadoc)
*
* @see me.springframework.di.Sink#getSource()
*/
public Source getSource() {
return source;
}
/*
* (non-Javadoc)
*
* @see me.springframework.di.Typed#getType()
*/
public String getType() {
return "java.lang.Object";
}
/*
* (non-Javadoc)
*
* @see me.springframework.di.Typed#isPrimitive()
*/
public boolean isPrimitive() {
return false;
}
public String getCastTo() {
return null;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
return "the " + index + "th element of " + source.toString();
}
}
/**
* Returns a {@link Configuration} from the instances passed in.
*
* @param instances
* The root instances for which we need a {@link Configuration}.
* @return The {@link Configuration} from the instances passed in.
*/
protected static Configuration createConfiguration(MutableContext context) {
Map<String, MutableInstance> instances = context.getInstances();
return new MutableConfiguration(instances);
}
}